﻿using System;
using System.Collections.Generic;
using PlusPlusPaint.FileIO.AST;

namespace PlusPlusPaint.FileIO
{
    /// <summary>
    /// Parser for data storing an image composed of layers.
    /// </summary>
    public class LayeredDataParser
    {
        /* This class is an LL(1) parser for a context-free grammar we're
         * using to store the information about the layered image.  The
         * grammar has S as the starting variable, and the following rules:
         * 
         * 0.  S   ->  "<<<" N ">>>"
         * 1.  N   ->  "(" NID AL ")"
         * 2.  AL  ->  AB AL
         * 3       ->
         * 4.  AB  ->  LID ":" A
         * 5.      ->  A
         * 6.  A   ->  N
         * 7.      ->  STR
         * 8.      ->  INT
         * 9.  NID ->  ID
         * 10. LID ->  ID
         * 
         * Explanation of the terms:
         *  - S is the entire data
         *  - N is a single so in the hierarchy, with the first identifier
         *    being its type
         *  - AL is a list of arguments for the so
         *  - AL is the tail of a list of arguments for the so
         *  - AB is a combination of an optional label and an argument
         *  - A is a single argument for the so
         *  - NID is a so identifier
         *  - LID is a label identifier
         *  - ID is a generic identifier (letters, underscares)
         *  - STR is a string (quotes, any content, backslash escapes)
         *  - INT is an int (positive or negative, 32 bit)
         * 
         * The type of a so should be the ObjectTypeName of a class that implements
         * INode.  Arguments without a label will be passed in the order they
         * appear, and the labelled arguments will be passed as a dictionary
         * of labels and arguments.
         * 
         * Parsing table:
         * - S +
         *   "<<<" => 0
         * - N +
         *   "(" => 1
         * - AL +
         *   ID => 2
         *   INT => 2
         *   STR => 2
         *   "(" => 2
         *   ")" => 3
         *   Other => 3 (invalid)
         * - AB +
         *   ID => 4
         *   INT => 5
         *   STR => 5
         *   "(" => 5
         *   Other => 5 (invalid)
         * - A +
         *   INT => 8
         *   STR => 7
         *   "(" => 6
         * - NID +
         *   ID => 9
         * - LID +
         *   ID => 10
         */

        delegate void ParserRule(Token tk);

        public enum NonTerminals
        {
            Start = 100,
            Node,
            ArgList,
            ArgBundle,
            Arg,
            NodeId,
            LabelId
        }

        #region member variables
        readonly IDictionary<NonTerminals, IDictionary<Tokens, ParserRule>> ruleTable = new Dictionary<NonTerminals, IDictionary<Tokens, ParserRule>>();
        Stack<NonTerminals> parseStack = new Stack<NonTerminals>();
        Stack<string> labelStack = new Stack<string>();
        LayeredDataLexer lexer = new LayeredDataLexer();
        TreeFactory treebuilder = new TreeFactory();
        #endregion

        #region constructors
        public LayeredDataParser(string input = "")
        {
            lexer.Input = input;
            Push(NonTerminals.Start);
            initializeRuleTable();
        }

        private void initializeRuleTable()
        {
            foreach (NonTerminals type in Enum.GetValues(typeof(NonTerminals)))
                this.ruleTable[type] = new Dictionary<Tokens, ParserRule>();
            { // S
                IDictionary<Tokens, ParserRule> d = this.ruleTable[NonTerminals.Start];
                d[Tokens.BeginDocument] = this.S_To_BD_N_ED;
            }
            { // N
                IDictionary<Tokens, ParserRule> d = this.ruleTable[NonTerminals.Node];
                d[Tokens.OpeningParen] = this.N_To_OP_NID_AL_CP;
            }
            { // AL
                IDictionary<Tokens, ParserRule> d = this.ruleTable[NonTerminals.ArgList];
                d[Tokens.Identifier] = this.AL_To_AB_AL;
                d[Tokens.Integer] = this.AL_To_AB_AL;
                d[Tokens.String] = this.AL_To_AB_AL;
                d[Tokens.OpeningParen] = this.AL_To_AB_AL;
                d[Tokens.ClosingParen] = this.AL_To_Empty;
            }
            { // AB
                IDictionary<Tokens, ParserRule> d = this.ruleTable[NonTerminals.ArgBundle];
                d[Tokens.Identifier] = this.AB_To_LID_CLN_A;
                d[Tokens.Integer] = this.AB_To_A;
                d[Tokens.String] = this.AB_To_A;
                d[Tokens.OpeningParen] = this.AB_To_A;
            }
            { // A
                IDictionary<Tokens, ParserRule> d = this.ruleTable[NonTerminals.Arg];
                d[Tokens.Integer] = A_To_INT;
                d[Tokens.String] = A_To_STR;
                d[Tokens.OpeningParen] = A_To_N;
            }
            { // NID
                IDictionary<Tokens, ParserRule> d = this.ruleTable[NonTerminals.NodeId];
                d[Tokens.Identifier] = NID_To_ID;
            }
            { // LID
                IDictionary<Tokens, ParserRule> d = this.ruleTable[NonTerminals.LabelId];
                d[Tokens.Identifier] = LID_To_ID;
            }
        }
        #endregion

        #region public methods
        public void Reset(string newinput)
        {
            parseStack.Clear();
            Push(NonTerminals.Start);
            lexer.Input = newinput;
            treebuilder.Reset();
        }

        public SerializedObject Parse()
        {
            Token current = lexer.RetrieveNextToken();
            while (parseStack.Count != 0)
            {
                if (AreEqual(parseStack.Peek(), current.Type))
                {
                    parseStack.Pop();
                    current = lexer.RetrieveNextToken();
                }
                else if (IsValidCombination(parseStack.Peek(), current.Type))
                {
                    ruleTable[parseStack.Peek()][current.Type](current);
                }
                else
                {
                    string onStack, inInput;
                    if ((int)parseStack.Peek() < 100)
                        onStack = Enum.GetName(typeof(Tokens), parseStack.Peek());
                    else
                        onStack = Enum.GetName(typeof(NonTerminals), parseStack.Peek());
                    inInput = Enum.GetName(typeof(Tokens), current.Type);
                    throw new Exception("Incompatible combination: " + onStack + ", " + inInput + " (" + current.Value + ").");
                }
            }
            return treebuilder.ReturnTree().ToSerializedObject();
        }
        #endregion

        #region parse handlers
        //
        // Important!  Don't forget to push things in reverse order!
        //

        // S -> "<<<" N ">>>"
        private void S_To_BD_N_ED(Token tk)
        {
            Pop();
            Push(Tokens.EndDocument);
            Push(NonTerminals.Node);
            Push(Tokens.BeginDocument);
            treebuilder.CreateNewNode(new TopLevelNode());
            treebuilder.AssociateLabel("");
        }

        // N -> "(" NID AL ")"
        private void N_To_OP_NID_AL_CP(Token tk)
        {
            Pop();
            Push(Tokens.ClosingParen);
            Push(NonTerminals.ArgList);
            Push(NonTerminals.NodeId);
            Push(Tokens.OpeningParen);
        }

        // AL -> AB AL
        private void AL_To_AB_AL(Token tk)
        {
            Pop();
            Push(NonTerminals.ArgList);
            Push(NonTerminals.ArgBundle);
        }

        // AL -> ""
        private void AL_To_Empty(Token tk)
        {
            Pop();
            treebuilder.FinishAndAddTopNodeAsArg();
        }

        // AB -> LID ":" A
        private void AB_To_LID_CLN_A(Token tk)
        {
            Pop();
            Push(NonTerminals.Arg);
            Push(Tokens.Colon);
            Push(NonTerminals.LabelId);
        }

        // AB -> A
        private void AB_To_A(Token tk)
        {
            Pop();
            treebuilder.AssociateLabel("");
            Push(NonTerminals.Arg);
        }

        // A -> N
        private void A_To_N(Token tk)
        {
            Pop();
            Push(NonTerminals.Node);
        }

        // A -> STR
        private void A_To_STR(Token tk)
        {
            Pop();
            treebuilder.AddArgToTopNode(LayeredDataLexer.DeserializeString(tk.Value));
            Push(Tokens.String);
        }

        // A -> INT
        private void A_To_INT(Token tk)
        {
            Pop();
            treebuilder.AddArgToTopNode(Int32.Parse(tk.Value));
            Push(Tokens.Integer);
        }

        // NID -> ID
        private void NID_To_ID(Token tk)
        {
            Pop();
            treebuilder.CreateNewNode(new CompositeNode(tk.Value));
            Push(Tokens.Identifier);
        }

        // LID -> ID
        private void LID_To_ID(Token tk)
        {
            Pop();
            treebuilder.AssociateLabel(tk.Value);
            Push(Tokens.Identifier);
        }
        #endregion

        #region private methods
        private void Push(NonTerminals t)
        {
            this.parseStack.Push(t);
        }

        private void Push(Tokens t)
        {
            this.parseStack.Push((NonTerminals)t);
        }

        private NonTerminals Pop()
        {
            return this.parseStack.Pop();
        }

        private bool AreEqual(NonTerminals nt, Tokens t)
        {
            return ((Tokens)nt == t);
        }

        private bool IsValidCombination(NonTerminals onStack, Tokens fromInput)
        {
            return
                this.ruleTable.ContainsKey(onStack) &&
                this.ruleTable[onStack].ContainsKey(fromInput);
        }
        #endregion
    }
}
